#include <MsHTML.h>
#include <atlbase.h>


#include <string>
#include <algorithm>
#include <memory>
#include <functional>


#include "Element.h"
#include "ElementFactory.h"
#include "Miscellaneous.h"

using namespace std;


CElement::CElement(void)
{
}


CElement::~CElement(void)
{
}

CComQIPtr<IHTMLElement> CElement::FindElement(IHTMLDocument2* document) const throw(...)
{
	HRESULT hr;
	CComQIPtr<IHTMLElement> e;
	CComQIPtr<IHTMLDocument3> spDoc3;
	//

	spDoc3 = document;
	if(!spDoc3)
		throw wstring(L"Cannot find query IHTMLDocument3 interface");	

	auto id = _mid.find(L"id");
	if(id != _mid.cend())
	{
		hr = spDoc3->getElementById(CComBSTR(id->second.c_str()), &e);
		if(FAILED(hr) || !e)
			throw wstring(L"Cannot find the specific element by id ->") + id->second;
	}
	else
	{
		CComQIPtr<IHTMLElementCollection> spColl;
		long nLength = 0;

		hr = spDoc3->getElementsByTagName(CComBSTR(_tag.c_str()), &spColl);
		if(FAILED(hr) || !spColl)
			throw wstring(L"Cannot find the specific element by tag name ->") + _tag;

		hr = spColl->get_length(&nLength);
		if(FAILED(hr) || !nLength)
			throw wstring(L"Cannot find the specific element by tag name, Length -> 0");

		for(int i=0; i<nLength; ++i)
		{
			CComDispatchDriver spdisp;
			CComQIPtr<IHTMLElement> spEm;

			hr = spColl->item(CComVariant(i), CComVariant(i), &spdisp);
			if(FAILED(hr) || !spdisp)
				throw wstring(L"Cannot get DispatchDriver from IHTMLElementCollection");

			spEm = spdisp;
			if(!spEm)
				throw wstring(L"Cannot find query IHTMLElement interface");	

			auto pair = find_if_not(_mid.cbegin(), _mid.cend(), [&spEm](map<wstring, wstring>::const_reference m) -> bool {
				HRESULT hr;
				CComVariant value;

				hr = spEm->getAttribute(CComBSTR(m.first.c_str()), 0 /*not case-sensitive*/, &value);
				if(FAILED(hr))
					throw std::wstring(L"Cannot retrieve attribute when finding an element, attribute ->") + m.first;

				if(value.bstrVal && m.second == value.bstrVal)
					return true;
				else
					return false;
			});

			if(pair == _mid.cend()) // found
			{
				if(e)
					e.Release();

				e = spEm;
				break;
			}
		}
	}


	if(!e)
		throw wstring(L"Cannot find the specific element by the pre-defined collection");	

	//
	return e;
}


void CElement::InitElementFromXml(IXmlReader* pReader) throw(...)
{
	HRESULT hr;
	XmlNodeType nodeType;
	UINT nCount;

	_tag = CMiscellaneous::GetLocalName(pReader);	
	this->BuildAvailableAttributes();


	hr = pReader->GetAttributeCount(&nCount);
	if(FAILED(hr))
		throw wstring(L"Cannot GetAttributeCount, tag ->") + _tag;

	hr = pReader->MoveToFirstAttribute();
	if(FAILED(hr))
		throw wstring(L"Cannot MoveToFirstAttribute, tag ->") + _tag;

	do
	{
		auto attribute = CMiscellaneous::GetAttributePair(pReader);
		//
		auto it = _maa.find(attribute.first);
		if(it == _maa.cend())
		{
			// put it into _mid
			_mid[attribute.first] = attribute.second;
		}
		else
		{
			// put it into _maa
			_maa[attribute.first] = attribute.second;
		}
		//
	}
	while(SUCCEEDED(hr = pReader->MoveToNextAttribute()) && --nCount);

	hr = pReader->MoveToElement();
	if(FAILED(hr))
		throw wstring(L"Cannot MoveToElement, tag ->") + _tag;

	// validate!
	this->ValidateAttributes();

	// get child element if any
	if (!pReader->IsEmptyElement())
		while (S_OK == (hr = pReader->Read(&nodeType)))
		{
			bool skip = false;
			switch (nodeType)
			{
			case XmlNodeType_Element:
				{
					_ve.push_back(CElementFactory::Create(pReader));
				}
				break;
			case XmlNodeType_EndElement:
				{
					if(CMiscellaneous::GetLocalName(pReader) == _tag)
						skip = true;
				}
				break;
			}
			if (skip)
				break;
		}

	//
}

bool CElement::IsActiveX() const throw()
{
	return this->_tag == L"object";
}